#include "lib/drivers/pvmdriver_winmm.h"
#include "lib/drivers/pvmdeviceinterface.h"
#include "lib/pvmerrors.h"
#include "lib/pvmstrings.h"
#include "lib/pvmdriver.h"

#include "lib/memory/pvmmemory.h"

#define PVMDWINMM_MAX_WAVEBLOCKS			10
#define PVMDWINMM_DEFAULT_BUFFER_LENGTH	200
#define PVMDWINMM_WAIT_FOR_SUSPEND		1000
#define PVMDWINMM_DEVICE_TYPE	EPVM_DTYPE_OUTPUT|EPVM_DTYPE_DEVICE

void CALLBACK WaveOutCallback(HWAVE hWave, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2);

class PVMDeviceWinWaveOut : public IPVMDeviceOutSimple, public PVMAllocableObject
{
public:
	PVMDeviceWinWaveOut(PVMDriverWinWaveOut* driver);
	virtual ~PVMDeviceWinWaveOut();

	virtual pvm_uint32 GetDeviceType();
	virtual void DeviceClose();
	virtual pvm_bool DeviceOpened();

	virtual pvm_bool SetOption(const pvm_char* optionName, void* param, pvm_uint32 paramSize);
	virtual pvm_bool GetOption(const pvm_char* optionName, void* param, pvm_uint32* paramSize);

	virtual void Suspend();
	virtual pvm_bool IsSuspended();
	virtual void Resume();

	virtual void DeviceNotify();

	virtual void SetBufferLengthMs(pvm_uint32 bufferSize);
	virtual pvm_uint32 GetBufferRealLengthMs();

	virtual pvm_int32 GetCPUUsage();

	virtual pvm_uint32 GetChanelsNum();
	virtual pvm_uint32 GetBitsPerSample();
	virtual pvm_uint32 GetSamplesPerSecond();

	pvm_bool Init(pvm_uint32 samplesPerSec, pvm_uint32 bitsPerSample, pvm_uint32 chanelsNum, pvm_int32 deviceNum, IPVMDeviceOutSimpleWaveSource *waveSource, pvm_bool deviceSuspended);

	virtual IPVMDriverBase* GetDriver();
protected:
	void DeInit();
	void PlaybackLoop();
	pvm_bool ResizeRenderBuffer(pvm_int newLengthMs);
	void FreeWaveHeader();
	void ProcessWaveOutCallback(HWAVE hWave, UINT uMsg, DWORD dwParam1, DWORD dwParam2);
	pvm_bool DeviceHasMoreData();

	static DWORD WINAPI PlaybackThreadProc(LPVOID lpParameter);
	friend void CALLBACK WaveOutCallback(HWAVE hWave, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2);
protected:
	pvm_bool mainVarsInitialized;
	CRITICAL_SECTION pvmdGlobalVarGuard;

	//threading vars
	HANDLE hPlayThread;
	DWORD dwPlayThreadId;

	pvm_bool runThread;
	pvm_bool isThreaRunning;

	pvm_bool pauseRendering;
	pvm_bool isRenderingPaused;

	//wave out stuff
	CRITICAL_SECTION	waveOutGuard;
	pvm_uint32			currentSampleRate;
	pvm_uint32			currentbitsPerSample;
	pvm_uint32			currentbytesPerSample;
	pvm_uint32			currentChanelsNum;
	HWAVEOUT			waveOutDev;
	pvm_int				scheduledBlocks;
	pvm_int				playedWaveHeadersCount;          // free index
	WAVEHDR*			playedWaveHeaders[PVMDWINMM_MAX_WAVEBLOCKS];
	WAVEHDR				waveBlocks[PVMDWINMM_MAX_WAVEBLOCKS];
	pvm_int				currentbuffer;
	pvm_int				prevSamplesRendered;

	pvm_uint32 currentBufferLength;
	pvm_uint32 newBufferLength;

	pvm_int renderBufferSize;
	pvm_int renderBufferSizeInSamples;

	DWORD	dwLastThreadTime;
	DWORD	dwLastTickTime;

	IPVMDeviceOutSimpleWaveSource *waveSource;
	PVMDriverWinWaveOut* driver;
};

pvm_uint32 PVMDeviceWinWaveOut::GetChanelsNum()
{
	return currentChanelsNum;
}

pvm_uint32 PVMDeviceWinWaveOut::GetBitsPerSample()
{
	return currentbitsPerSample;
}

pvm_uint32 PVMDeviceWinWaveOut::GetSamplesPerSecond()
{
	return currentSampleRate;
}

IPVMDriverBase* PVMDeviceWinWaveOut::GetDriver()
{
	return driver;
}

PVMDeviceWinWaveOut::PVMDeviceWinWaveOut(PVMDriverWinWaveOut* driver)
{
	mainVarsInitialized = FALSE;

	InitializeCriticalSection(&pvmdGlobalVarGuard);

	//threading vars
	hPlayThread = NULL;
	dwPlayThreadId = NULL;

	runThread = FALSE;
	isThreaRunning = FALSE;

	pauseRendering = FALSE;
	isRenderingPaused = FALSE;

	waveOutGuard;
	currentSampleRate = 0;
	currentbitsPerSample = 0;
	currentbytesPerSample = 0;
	currentChanelsNum = 0;
	waveOutDev = NULL;
	scheduledBlocks        = 0;
	playedWaveHeadersCount = 0;
	currentbuffer = 0;


	currentBufferLength = 0;
	newBufferLength = PVMDWINMM_DEFAULT_BUFFER_LENGTH;

	renderBufferSize = 0;
	renderBufferSizeInSamples = 0;

	for(pvm_int i=0; i<PVMDWINMM_MAX_WAVEBLOCKS; i++)
	{
		memset(&waveBlocks[i], 0, sizeof(waveBlocks[i]));
	}

	dwLastThreadTime = 0;
	dwLastTickTime = 0;

	waveSource = NULL;
	this->driver = driver;
}

PVMDeviceWinWaveOut::~PVMDeviceWinWaveOut()
{
	DeInit();
	if(runThread)
		memset(this, 0, sizeof(this));//just in case
}

static DWORD GetThreadTick(FILETIME* a, FILETIME* b)
{
	__int64 a64 = 0;
	__int64 b64 = 0;
	a64 = a->dwHighDateTime;
	a64 <<= 32;
	a64 += a->dwLowDateTime;

	b64 = b->dwHighDateTime;
	b64 <<= 32;
	b64 += b->dwLowDateTime;

	a64 += b64;

	// nano sec to milli sec
	a64 /= 10000;

	return (DWORD)a64;
}


pvm_int32 PVMDeviceWinWaveOut::GetCPUUsage()
{
	DWORD dwCurrentThreadTime =0;
	DWORD dwCurrentTickTime = 0;

	FILETIME ftCreationTime;
	FILETIME ftExitTime;
	FILETIME ftKernelTime;
	FILETIME ftUserTime;

	DWORD dwCpuPower;

	if(!isThreaRunning)
		return 0;

	dwCurrentTickTime = GetTickCount();
	GetThreadTimes(hPlayThread, &ftCreationTime, &ftExitTime, &ftKernelTime, &ftUserTime);
	dwCurrentThreadTime = GetThreadTick(&ftKernelTime, &ftUserTime);

	// calculate cpu power
	if( dwCurrentTickTime != dwLastTickTime && (dwLastThreadTime != 0 || dwLastTickTime != 0))
		dwCpuPower = 100 - (((dwCurrentThreadTime - dwLastThreadTime) * 100) / (dwCurrentTickTime - dwLastTickTime));
	else
		dwCpuPower = 0;	// avoid 0div

	dwLastTickTime = dwCurrentTickTime;
	dwLastThreadTime = dwCurrentThreadTime;

	return 100 - dwCpuPower;
}

pvm_bool PVMDeviceWinWaveOut::Init(pvm_uint32 samplesPerSec, pvm_uint32 bitsPerSample, pvm_uint32 chanelsNum, pvm_int32 deviceNum, IPVMDeviceOutSimpleWaveSource *waveSource, pvm_bool deviceSuspended)
{
	EnterCriticalSection(&pvmdGlobalVarGuard);

	mainVarsInitialized = FALSE;
	runThread = TRUE;
	isThreaRunning = FALSE;

	pauseRendering = FALSE;
	isRenderingPaused = FALSE;	

	for(pvm_int i=0; i<PVMDWINMM_MAX_WAVEBLOCKS; i++)
	{
		memset(&waveBlocks[i], 0, sizeof(waveBlocks[i]));
	}

	pauseRendering = deviceSuspended;

	hPlayThread = CreateThread(NULL, 16384, PlaybackThreadProc, (void*)this, 0, &dwPlayThreadId); 

	LeaveCriticalSection(&pvmdGlobalVarGuard);

	if(!hPlayThread)
	{
		DeleteCriticalSection(&pvmdGlobalVarGuard);
		return false;
	}

	SetThreadPriority(hPlayThread, THREAD_PRIORITY_HIGHEST);

	EnterCriticalSection(&pvmdGlobalVarGuard);
	mainVarsInitialized = TRUE;
	LeaveCriticalSection(&pvmdGlobalVarGuard);


	WAVEFORMATEX  outFormat;
	DWORD          deviceID = WAVE_MAPPER;

	DWORD devicesCount = waveOutGetNumDevs();
	if(0==devicesCount)
		return false;

	if(deviceNum!=-1||deviceNum>=0)
	{
		deviceID = deviceNum;
		if(deviceID>=devicesCount)
		{
			runThread = false;
			return false;
		}
	}
	else if(deviceNum<-1)
	{
		runThread = false;
		return false;
	}

	outFormat.wFormatTag      = WAVE_FORMAT_PCM;
	outFormat.wBitsPerSample  = (WORD)bitsPerSample;
	outFormat.nChannels       = (WORD)chanelsNum;
	outFormat.nSamplesPerSec  = samplesPerSec;
	outFormat.nBlockAlign     = (outFormat.wBitsPerSample + 7) / 8 * outFormat.nChannels;
	outFormat.nAvgBytesPerSec = outFormat.nSamplesPerSec * outFormat.nBlockAlign;

	EnterCriticalSection(&pvmdGlobalVarGuard);
	waveOutDev = NULL;
	LeaveCriticalSection(&pvmdGlobalVarGuard);

	currentSampleRate = samplesPerSec;
	currentbitsPerSample = bitsPerSample;
	currentbytesPerSample = bitsPerSample/8;
	currentChanelsNum = chanelsNum;

	InitializeCriticalSection(&waveOutGuard);

	MMRESULT mmResult = waveOutOpen(&waveOutDev, deviceID, &outFormat, (DWORD)WaveOutCallback, (DWORD)this, CALLBACK_FUNCTION);

	EnterCriticalSection(&pvmdGlobalVarGuard);
	if(MMSYSERR_NOERROR!=mmResult)
	{
		DeleteCriticalSection(&waveOutGuard);

		currentSampleRate = 0;
		currentbitsPerSample = 0;
		currentbytesPerSample = 0;
		currentChanelsNum = 0;
	}

	LeaveCriticalSection(&pvmdGlobalVarGuard);

	if(mmResult!=MMSYSERR_NOERROR)
	{
		runThread = false;
		return false;
	}

	waveOutReset(waveOutDev);
	this->waveSource = waveSource;
	DeviceNotify();

	return true;
}

void PVMDeviceWinWaveOut::DeInit()
{
	if(DeviceOpened())
	{
		//close wave out
		pauseRendering = TRUE;
		while(scheduledBlocks>0)
		{
			Sleep(scheduledBlocks);
			while(playedWaveHeadersCount>0)         // free used blocks ...
				FreeWaveHeader();
		}
	}

	if(mainVarsInitialized&&runThread)
	{
		EnterCriticalSection(&pvmdGlobalVarGuard);
		runThread  = FALSE;
		LeaveCriticalSection(&pvmdGlobalVarGuard);


		if(isRenderingPaused)
			Resume();

		int resumeCount = 10;
		while(isThreaRunning)
		{
			Resume();
			if(WaitForSingleObject(hPlayThread, 100)==WAIT_OBJECT_0)
				isThreaRunning = false;
			else
			{
				resumeCount--;
				if(resumeCount<=0)
				{
					TerminateThread(hPlayThread, -1);
				}
				isThreaRunning = false;
			}
		}
	}

	if(DeviceOpened())
	{
		//Thread has been stopped, so we don't need to call synchronization functions.
		
		waveOutReset(waveOutDev);      // reset the device
		waveOutClose(waveOutDev);      // close the device
		waveOutDev = NULL;
		scheduledBlocks = 0;

		DeleteCriticalSection(&waveOutGuard);
	}

	if(mainVarsInitialized)
	{
		EnterCriticalSection(&pvmdGlobalVarGuard);
		mainVarsInitialized = false;

		for(pvm_int i=0; i<PVMDWINMM_MAX_WAVEBLOCKS; i++)
		{
			//ToDo:unaligned data exception
			LocalFree(waveBlocks[i].lpData);
			pvm_int size = sizeof(WAVEHDR);
			memset(&waveBlocks[i], 0, size);
		}

		LeaveCriticalSection(&pvmdGlobalVarGuard);
		DeleteCriticalSection(&pvmdGlobalVarGuard);
	}

	waveSource = NULL;
}

DWORD WINAPI PVMDeviceWinWaveOut::PlaybackThreadProc(LPVOID lpParameter)
{
	((PVMDeviceWinWaveOut*)lpParameter)->PlaybackLoop();	
	return 0;
}

pvm_uint32 PVMDeviceWinWaveOut::GetDeviceType()
{
	return PVMDWINMM_DEVICE_TYPE;
}

void PVMDeviceWinWaveOut::DeviceClose()
{
	DeInit();
	delete(this);
}

pvm_bool PVMDeviceWinWaveOut::DeviceOpened()
{
	return waveOutDev!=NULL;
}

pvm_bool PVMDeviceWinWaveOut::SetOption(const pvm_char* optionName, void* param, pvm_uint32 paramSize)
{
	return false;
}

pvm_bool PVMDeviceWinWaveOut::GetOption(const pvm_char* optionName, void* param, pvm_uint32* paramSize)
{
	return false;
}

void PVMDeviceWinWaveOut::Suspend()
{
	if(isRenderingPaused&&pauseRendering)
		return;
	
	EnterCriticalSection(&pvmdGlobalVarGuard);
	pauseRendering = TRUE;
	LeaveCriticalSection(&pvmdGlobalVarGuard);

	DWORD time1 = GetTickCount();
	DWORD time2;
	if(hPlayThread)
	{
		while(!isRenderingPaused)
		{
			time2 = GetTickCount();
			if(time2-time1>PVMDWINMM_WAIT_FOR_SUSPEND)
				break;
			//check for DWORD overflow - will no hang but will wait more than WAIT_FOR_SUSPEND
			if(time1>time2)
				time1 = time2;

			Sleep(0);
		}
	}
}

pvm_bool PVMDeviceWinWaveOut::IsSuspended()
{
	return isRenderingPaused&&pauseRendering;
}

void PVMDeviceWinWaveOut::Resume()
{
	EnterCriticalSection(&pvmdGlobalVarGuard);
	pauseRendering = FALSE;
	LeaveCriticalSection(&pvmdGlobalVarGuard);

	if(isRenderingPaused)
	{
		EnterCriticalSection(&pvmdGlobalVarGuard);
		isRenderingPaused = FALSE;
		LeaveCriticalSection(&pvmdGlobalVarGuard);
		ResumeThread(hPlayThread);
	}
}

void PVMDeviceWinWaveOut::DeviceNotify()
{
	if(!pauseRendering&&isRenderingPaused)
	{
		//EnterCriticalSection(&globalVarGuard);
		isRenderingPaused = FALSE;
		//LeaveCriticalSection(&globalVarGuard);
		if(hPlayThread)
			ResumeThread(hPlayThread);
	}
}

void PVMDeviceWinWaveOut::SetBufferLengthMs(pvm_uint32 bufferSize)
{
	newBufferLength = bufferSize;
	DeviceNotify();
}

pvm_uint32 PVMDeviceWinWaveOut::GetBufferRealLengthMs()
{
	return currentBufferLength;
}

PVM_FORCEINLINE pvm_bool PVMDeviceWinWaveOut::DeviceHasMoreData()
{
	if(prevSamplesRendered!=0)
		return true;

	return waveSource->HasMoreData();
}

void PVMDeviceWinWaveOut::PlaybackLoop()
{
	prevSamplesRendered = 0;
	isThreaRunning = true;

	pvm_bool needSuspendThread = FALSE;
	WAVEHDR *wh;
	pvm_bool isWaveOutError = FALSE;
	pvm_bool srcReady = false;

	//SetThreadPriority(GetCurrentThread (), THREAD_PRIORITY_ABOVE_NORMAL);
	//SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_ABOVE_NORMAL);

	while(runThread)
	{
		needSuspendThread = FALSE;

		if(srcReady==false)
		{
			if(waveSource==NULL || waveSource->IsInitialized()==false)
				needSuspendThread = TRUE;
			else
				srcReady = true;
		}
		else if((pauseRendering&&!isRenderingPaused)||(!waveOutDev)||!DeviceHasMoreData())
		{
			needSuspendThread = TRUE;
		}

		if(needSuspendThread)
		{
			EnterCriticalSection(&pvmdGlobalVarGuard);
			isRenderingPaused = TRUE;
			needSuspendThread = FALSE;
			LeaveCriticalSection(&pvmdGlobalVarGuard);
			SuspendThread(GetCurrentThread());
		}

		if((currentBufferLength!=newBufferLength) && (waveSource!=NULL) && (waveSource->IsInitialized()!=false))
		{
			if(!ResizeRenderBuffer(newBufferLength))
			{
				//SetLastLibError(PVERR_CANT_SET_PLAY_BUFFER_SIZE);
				//handle error
			}
		}

		if(playedWaveHeadersCount>0)
			FreeWaveHeader();

		if(waveSource!=NULL&&DeviceHasMoreData()&&scheduledBlocks<PVMDWINMM_MAX_WAVEBLOCKS)
		{
			//tt1 = GetTickCount();
			while(scheduledBlocks<PVMDWINMM_MAX_WAVEBLOCKS)
			{
				wh = &(waveBlocks[currentbuffer]);
				//memset(wh->lpData, 0, wh->dwBufferLength);
				prevSamplesRendered = waveSource->FillBuffer(wh->lpData, renderBufferSizeInSamples);

				isWaveOutError = FALSE;
				MMRESULT mmRes = waveOutPrepareHeader(waveOutDev, wh, sizeof (WAVEHDR));
				if(mmRes != MMSYSERR_NOERROR)
				{
					isWaveOutError = TRUE;
				}
				mmRes = waveOutWrite(waveOutDev, wh, sizeof (WAVEHDR));
				if(mmRes != MMSYSERR_NOERROR)
				{
					isWaveOutError = TRUE;
				}
				if(!isWaveOutError)
				{
					EnterCriticalSection(&waveOutGuard);
					scheduledBlocks++;
					currentbuffer = (currentbuffer+1)%PVMDWINMM_MAX_WAVEBLOCKS;
					wh->dwUser = 1;
					LeaveCriticalSection(&waveOutGuard);
				}
				Sleep(0);
			}
			//end of rendering
		}
		else
		{
			//Cache?
			//EnterCriticalSection(&pvmdRenderGuard);
			//LeaveCriticalSection(&pvmdRenderGuard);
			Sleep(0);
		}
		Sleep(1);
	}

	Sleep(100);

	EnterCriticalSection(&pvmdGlobalVarGuard);
	isThreaRunning = FALSE;
	pauseRendering = FALSE;
	isRenderingPaused = FALSE;
	LeaveCriticalSection(&pvmdGlobalVarGuard);
}

void PVMDeviceWinWaveOut::FreeWaveHeader()
{
	WAVEHDR*  wh;

	EnterCriticalSection(&waveOutGuard);
	wh = playedWaveHeaders[--playedWaveHeadersCount];
	scheduledBlocks--;                               // decrease the number of USED blocks
	LeaveCriticalSection(&waveOutGuard);

	waveOutUnprepareHeader(waveOutDev, wh, sizeof (WAVEHDR));

	waveBlocks[wh->dwUser].dwUser = 0;
	DeviceNotify();
}

pvm_bool PVMDeviceWinWaveOut::ResizeRenderBuffer(pvm_int newLengthMs)
{
	EnterCriticalSection(&waveOutGuard);
	pvm_int sampleRate = currentSampleRate;
	pvm_int sampleSize = currentbytesPerSample;
	pvm_int chanelsNum = currentChanelsNum;
	LeaveCriticalSection(&waveOutGuard);

	pvm_int renderStepSize = sampleSize*chanelsNum;
	pvm_int newRenderBufferSizeInBites = sampleRate*renderStepSize*newLengthMs/(1000*PVMDWINMM_MAX_WAVEBLOCKS);
	//buffer size must be multiple of chanelNum*sampleSize
	newRenderBufferSizeInBites = (newRenderBufferSizeInBites/renderStepSize)*renderStepSize;


	while((scheduledBlocks>0)&&(playedWaveHeadersCount>0))
	{
		FreeWaveHeader();
		if((playedWaveHeadersCount==0)&&(scheduledBlocks>0))
			Sleep (26);
	}

	renderBufferSize = newRenderBufferSizeInBites;
	renderBufferSizeInSamples = renderBufferSize/sampleSize;

	currentBufferLength = newLengthMs;

	//now we can resize memory of our pool
	for(pvm_int i=0; i<PVMDWINMM_MAX_WAVEBLOCKS; i++)
	{
		LocalFree(waveBlocks[i].lpData);
		waveBlocks[i].dwBufferLength = newRenderBufferSizeInBites;
		waveBlocks[i].lpData = (LPSTR)LocalAlloc(LMEM_FIXED, newRenderBufferSizeInBites);;
		waveBlocks[i].dwUser = 0;
	}

	if(waveSource!=NULL)
		waveSource->ResizeRenderBuffer(renderBufferSize);

	return TRUE;
}

void CALLBACK WaveOutCallback(HWAVE hWave, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
{	
	((PVMDeviceWinWaveOut*)dwInstance)->ProcessWaveOutCallback(hWave, uMsg, dwParam1, dwParam2);
}

void PVMDeviceWinWaveOut::ProcessWaveOutCallback(HWAVE hWave, UINT uMsg, DWORD dwParam1, DWORD dwParam2)
{
	if(uMsg==WOM_DONE)
	{
		EnterCriticalSection(&waveOutGuard);
		playedWaveHeaders[playedWaveHeadersCount++] = (WAVEHDR*)dwParam1;
		LeaveCriticalSection(&waveOutGuard);
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////
//Driver
////////////////////////////////////////////////////////////////////////////////////////////////

PVMDriverWinWaveOut::PVMDriverWinWaveOut()
{
}

PVMDriverWinWaveOut::~PVMDriverWinWaveOut()
{
}

pvm_bool PVMDriverWinWaveOut::Init()
{
	return true;
}

pvm_bool PVMDriverWinWaveOut::IsInitialized()
{
	return true;
}

void PVMDriverWinWaveOut::DriverClose()
{
	delete(this);
}

pvm_uint32 PVMDriverWinWaveOut::GetDriverType()
{
	return PVMDWINMM_DEVICE_TYPE;
}

pvm_bool PVMDriverWinWaveOut::GetDriverOption(const pvm_char* optionName, void* optionVal, pvm_uint32* optionSize)
{
	return false;
}

pvm_bool PVMDriverWinWaveOut::SetDriverOption(const pvm_char* optionName, void* optionVal, pvm_uint32 optionSize)
{
	return false;
}

pvm_uint32 PVMDriverWinWaveOut::DevicesNum()
{
	return waveOutGetNumDevs();
}

pvm_int32 PVMDriverWinWaveOut::DeviceName(pvm_int32 nDevice, pvm_char* buffer, pvm_uint32 maxLength)
{
	pvm_int devNum = nDevice;
	if(devNum==-1)
		devNum = WAVE_MAPPER;

	pvm_int nDevices = waveOutGetNumDevs();

	if(devNum>nDevices||(devNum<0&&devNum!=WAVE_MAPPER))
		return -1;

	WAVEOUTCAPS caps;

	memset(&caps, 0, sizeof(caps));

	MMRESULT mmResult = waveOutGetDevCaps(devNum, &caps, sizeof(WAVEOUTCAPS));
	if(MMSYSERR_NOERROR!=mmResult)
	{
		return -1;
	}

	pvm_uint32 nameLen = (pvm_uint32)pvm_strlen((const pvm_char*)caps.szPname);

	if(buffer==NULL||maxLength==0)
	{
		return nameLen + 1;
	}

	if(maxLength<nameLen + 1)
		return 0;
	
#if defined(_UNICODE) || defined(UNICODE)
	memcpy(buffer, caps.szPname, (nameLen + 1)*sizeof(short));
#else
#error "Only UNICODE builds are supported"
#endif
	return nameLen;
}

pvm_bool PVMDriverWinWaveOut::DeviceEnumModes(pvm_int32 nDeivce, TFormatEnumCallbackProc enumProc, void *param)
{
	if(!enumProc)
		return false;

	WAVEOUTCAPS woc;

	UINT deviceID = WAVE_MAPPER;
	UINT devicesCount = waveOutGetNumDevs();

	if(0==devicesCount)
		return false;

	if(nDeivce!=-1)
	{
		if(nDeivce<0)
			return false;
		deviceID = nDeivce;
		if(deviceID>devicesCount)
			return false;
	}

	if(waveOutGetDevCaps(deviceID, &woc, sizeof(WAVEOUTCAPS))!=MMSYSERR_NOERROR)
		return false;

	if(woc.dwFormats&WAVE_FORMAT_1M08)// 11.025 kHz, mono, 8-bit 
		enumProc(1, 8, 11025, param);
	if(woc.dwFormats&WAVE_FORMAT_1M16)// 11.025 kHz, mono, 16-bit 
		enumProc(1, 16, 11025, param);
	if(woc.dwFormats&WAVE_FORMAT_1S08)// 11.025 kHz, stereo, 8-bit 
		enumProc(2, 11025, 8, param);
	if(woc.dwFormats&WAVE_FORMAT_1S16)// 11.025 kHz, stereo, 16-bit 
		enumProc(2, 11025, 16, param);
	if(woc.dwFormats&WAVE_FORMAT_2M08)// 22.05 kHz, mono, 8-bit 
		enumProc(1, 8, 22050, param);
	if(woc.dwFormats&WAVE_FORMAT_2M16)// 22.05 kHz, mono, 16-bit 
		enumProc(1, 16, 22050, param);
	if(woc.dwFormats&WAVE_FORMAT_2S08)// 22.05 kHz, stereo, 8-bit 
		enumProc(2, 8, 22050, param);
	if(woc.dwFormats&WAVE_FORMAT_2S16)// 22.05 kHz, stereo, 16-bit 
		enumProc(2, 16, 22050, param);
	if(woc.dwFormats&WAVE_FORMAT_4M08)// 44.1 kHz, mono, 8-bit 
		enumProc(1, 8, 44100, param);
	if(woc.dwFormats&WAVE_FORMAT_4M16)// 44.1 kHz, mono, 16-bit 
		enumProc(1, 16, 44100, param);
	if(woc.dwFormats&WAVE_FORMAT_4S08)// 44.1 kHz, stereo, 8-bit 
		enumProc(2, 8, 44100, param);
	if(woc.dwFormats&WAVE_FORMAT_4S16)// 44.1 kHz, stereo, 16-bit 
		enumProc(2, 16, 44100, param);

#ifndef _WIN32_WCE
	if(woc.dwFormats&WAVE_FORMAT_48M08)      // 48     kHz, Mono,   8-bit  
		enumProc(1, 8, 48000, param);
	if(woc.dwFormats&WAVE_FORMAT_48S08)      // 48     kHz, Stereo, 8-bit  
		enumProc(1, 16, 48000, param);
	if(woc.dwFormats&WAVE_FORMAT_48M16)      // 48     kHz, Mono,   16-bit 
		enumProc(2, 8, 48000, param);
	if(woc.dwFormats&WAVE_FORMAT_48S16)      // 48     kHz, Stereo, 16-bit 
		enumProc(2, 16, 48000, param);
	if(woc.dwFormats&WAVE_FORMAT_96M08)      // 96     kHz, Mono,   8-bit  
		enumProc(1, 8, 96000, param);
	if(woc.dwFormats&WAVE_FORMAT_96S08)      // 96     kHz, Stereo, 8-bit  
		enumProc(1, 16, 96000, param);
	if(woc.dwFormats&WAVE_FORMAT_96M16)      // 96     kHz, Mono,   16-bit 
		enumProc(2, 8, 96000, param);
	if(woc.dwFormats&WAVE_FORMAT_96S16)      // 96     kHz, Stereo, 16-bit 
		enumProc(2, 16, 96000, param);
#endif
	return true;
}

IPVMDeviceOutSimple* PVMDriverWinWaveOut::DeviceOpen(pvm_uint32 samplesPerSec, pvm_uint32 bitsPerSample, pvm_uint32 chanelsNum, pvm_int32 deviceNum, void *additionalInfo, pvm_uint32 infoSize, IPVMDeviceOutSimpleWaveSource *waveSource, pvm_bool deviceSuspended)
{
	PVMDeviceWinWaveOut* device = new PVMDeviceWinWaveOut(this);
	if(!device)
		return NULL;
	if(!device->Init(samplesPerSec, bitsPerSample, chanelsNum, deviceNum, waveSource, deviceSuspended))
	{
		device->DeviceClose();
		return NULL;
	}

	return device;
}

void PVMDriverWinWaveOut::CloseDevice(IPVMDeviceBase* device)
{
	if(device!=NULL)
		device->DeviceClose();
}

//////////////////////////////////////////////////////////////////////////

IPVMDriverOut* PVMDriverCreateWinWaveOut()
{
	PVMDriverWinWaveOut* waveOutDrv = new PVMDriverWinWaveOut();
	if(!waveOutDrv)
		return NULL;
	PVMDriverSimple* simpledrv = new PVMDriverSimple(waveOutDrv);
	if(!simpledrv)
	{
		waveOutDrv->DriverClose();
	}

	return simpledrv;
}

static pvm_int32 DriverNameShort(pvm_char* buffer, pvm_uint32 maxLength)
{
	return PVMCopyDriverName(_PVM_STR("WinMM"), buffer, maxLength);
}

static pvm_int32 DriverNameFull(pvm_char* buffer, pvm_uint32 maxLength)
{
	return PVMCopyDriverName(_PVM_STR("Windows Multimedia System"), buffer, maxLength);
}

static pvm_uint32 DriverTypeAndFlags()
{
	return PVMDWINMM_DEVICE_TYPE;
}

static IPVMDriverBase* DriverLoad()
{
	return PVMDriverCreateWinWaveOut();
}

IPVMDriverDesc	winMMDesc;

IPVMDriverDesc* PVMGetDriverDescWinMM()
{
	winMMDesc.DriverNameShort = DriverNameShort;
	winMMDesc.DriverNameFull = DriverNameFull;
	winMMDesc.DriverTypeAndFlags = DriverTypeAndFlags;
	winMMDesc.DriverLoad = DriverLoad;
	return &winMMDesc;
}

#undef	PVMDWINMM_MAX_WAVEBLOCKS
#undef	PVMDWINMM_DEFAULT_BUFFER_LENGTH
#undef	PVMDWINMM_WAIT_FOR_SUSPEND
#undef	PVMDWINMM_DEVICE_TYPE
